package org.datascooter.impl;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;

import org.datascooter.bundle.ContainerProvider;
import org.datascooter.bundle.DSMapper;
import org.datascooter.bundle.EntityBundle;
import org.datascooter.bundle.attribute.PersistReferenceAttribute;
import org.datascooter.cache.SimpleSnipCashe;
import org.datascooter.exception.DataManagerException;
import org.datascooter.exception.DataScooterException;
import org.datascooter.exception.EntityNotMappedException;
import org.datascooter.exception.SnipManagerException;
import org.datascooter.generator.IdManager;
import org.datascooter.inface.IBackupManager;
import org.datascooter.inface.IBundleChangeListener;
import org.datascooter.inface.IDataManager;
import org.datascooter.inface.IReplicationManager;
import org.datascooter.inface.ISnipBuilder;
import org.datascooter.inface.ISnipListener;
import org.datascooter.inface.ISnipManager;
import org.datascooter.meta.BackupManager;
import org.datascooter.meta.ReplicationManager;
import org.datascooter.utils.DSSettings;
import org.datascooter.utils.LangUtils;
import org.datascooter.utils.SnipUtils;
import org.datascooter.utils.policy.Function;
import org.datascooter.utils.policy.ReferencePolicy;
import org.datascooter.utils.policy.SnipPolicy;
import org.datascooter.utils.policy.SnipType;
import org.datascooter.utils.policy.TableValidationPolicy;

/**
 * This class is user entry point for system - it is a wrapperr for simplify access to SnipManager. SnipManager may be local or remote. SnipManager can working with only one type - Snip - DataManager
 * manage it process
 * 
 * @author nemo
 * 
 */
public final class DataManager implements IDataManager, IBundleChangeListener {

	private String transactionId;
	private ISnipManager snipManager;
	private List<ISnipListener> listeners = new ArrayList<ISnipListener>();
	private InstanceManager instanceManager;
	private SimpleSnipCashe casheManager;
	private IdManager idManager;
	private final ISnipBuilder builder;
	private ContainerProvider crossProvider;

	public DataManager(ISnipManager snipManagerIn, ISnipBuilder builder) throws EntityNotMappedException, SecurityException, NoSuchMethodException {
		this.snipManager = snipManagerIn;
		this.builder = builder;
		casheManager = new SimpleSnipCashe(builder);
		if (!DSSettings.getTableValidationPolicy().equals(TableValidationPolicy.NEVER)) {
			DSMapper.addBundleChangeListener(this);
		}
		crossProvider = new ContainerProvider(this);
		builder.setContainerProvider(crossProvider);
		instanceManager = new InstanceManager(this, crossProvider);
		if (DSSettings.isVerifyTables()) {
			verifyTables();
		}
		idManager = new IdManager(this);
		crossProvider.init();
		idManager.init();
		log("   ***Started DataManager:  " + getName());
	}

	private void log(String string) {
		Logger.getLogger(getName()).info(string);
	}

	@Override
	public void addSnipListener(ISnipListener listener) {
		listeners.add(listener);
	}

	@Override
	public void removeSnipListener(ISnipListener listener) {
		listeners.remove(listener);
	}

	/**
	 * Provides procedure of verifying of database tables
	 * 
	 * @throws EntityNotMappedException
	 */
	@Override
	public void verifyTables() {
		try {
			new ClientTableManager(this).verifyTables();
		} catch (Exception e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	@Override
	public double avg(Object obj, String attribute) {
		return function(obj, attribute, Function.AVG).doubleValue();
	}

	/**
	 * Deletes a list of objects
	 */
	@Override
	public void batchDelete(List<?> input) {
		if (input != null && input.size() > 0) {
			try {
				snipManager.execute(getBuilderInt().getCollectionSnip(input, SnipType.DELETE, transactionId, this));
			} catch (Exception e) {
				throw new DataScooterException(getManagerName(), e);
			}
		}
	}

	@Override
	public ISnipBuilder getBuilderInt() {
		ISnipBuilder copy = builder.copy();
		copy.setContainerProvider(crossProvider);
		return copy;
	}

	/**
	 * Saves a list of objects
	 */
	@Override
	public void batchSave(Collection<?> input) {
		try {
			if (input != null && input.size() > 0) {
				snipManager.execute(getBuilderInt().getCollectionSnip(input, SnipType.INSERT, transactionId, this));
			}
		} catch (Exception e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	@Override
	public void batchUpdate(Collection<?> input) {
		try {
			if (input != null && input.size() > 0) {
				snipManager.execute(getBuilderInt().getCollectionSnip(input, SnipType.UPDATE, transactionId, this));
			}
		} catch (Exception e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	@Override
	public void beginTransaction(String transactionId) {
		this.transactionId = transactionId;
		snipManager.beginTransaction(transactionId);
	}

	@Override
	public void bundleChanged(EntityBundle bundle) {
		if (DSSettings.getTableValidationPolicy().equals(TableValidationPolicy.BY_BUNDLE_CHANGE)) {
			verifyTable(bundle);
		}
	}

	@Override
	public void close() {
		snipManager.close();
	}

	@Override
	public void commit() {
		try {
			snipManager.commit();
			transactionId = null;
		} catch (SnipManagerException e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	@Override
	public DataSnip startSnip(Object obj, SnipType type) {
		DataSnip snip;
		if (obj instanceof DataSnip) {
			snip = (DataSnip) obj;
		} else {
			snip = new DataSnip(LangUtils.entityName(obj), type);
		}
		return snip;
	}

	@Override
	public int count(Object obj) {
		try {
			if (obj.getClass().equals(Snip.class)) {
				Object[][] data = snipManager.retrieve((Snip) obj).getData();
				if (data.length > 0 && data[0].length > 0) {
					return Long.valueOf(data[0][0] + "").intValue();
				}
				return 0;
			}
			DataSnip snip = startSnip(obj, SnipType.FUNCTION);
			if (!(obj instanceof String) && !(obj instanceof Class<?>) && !(obj instanceof DataSnip)) {
				snip.whereId(DSMapper.getIdValue(obj));
			}
			snip.setFunction(Function.COUNT);
			Object[][] data = snipManager.retrieve(casheManager.getCachedSnip(snip, null)).getData();
			if (data.length > 0 && data[0].length > 0) {
				return Long.valueOf(data[0][0] + "").intValue();
			}
			return 0;
		} catch (Exception e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	@Override
	public int count(Object obj, String attribute, Object value) {
		try {
			DataSnip snip = startSnip(obj, SnipType.FUNCTION);
			snip.where(attribute, value);
			snip.setFunction(Function.COUNT);
			return ((BigDecimal) snipManager.retrieve(casheManager.getCachedSnip(snip, null)).getData()[0][0]).intValue();
		} catch (Exception e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	/**
	 * Creates a table for given entity
	 */
	@Override
	public void create(Object obj) {
		if (obj.getClass().equals(Snip.class)) {
			try {
				snipManager.execute((Snip) obj);
				return;
			} catch (Exception e) {
				throw new DataManagerException(getManagerName(), e);
			}
		}
		execute(startSnip(obj, SnipType.CREATE));
	}

	/**
	 * Deletes entities parameter may be String -entityName,Object - entityInstance used for invoking a class name , Class - entity class or DataSnipFactory. If it will be instance - only one object
	 * will be deleted if string or class - all entities will be deleted
	 */
	@Override
	public void delete(Object obj) {
		try {
			if (obj == null) {
				return;
			}
			if (obj.getClass().equals(Snip.class)) {
				snipManager.execute((Snip) obj);
			} else {
				DataSnip snip = startSnip(obj, SnipType.DELETE);
				if (!(obj instanceof String) && !(obj instanceof Class<?>) && !(obj instanceof DataSnip)) {
					Object idValue = DSMapper.getIdValue(obj);
					snip.whereId(idValue);
					snip.objectId = idValue;
					instanceManager.provideChildrenLikeParent(snip, obj);
				}
				execute(snip);
			}
		} catch (Exception e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	@Override
	public void deleteReferences(Object obj) {
		try {
			if (obj == null) {
				return;
			}
			if (!obj.getClass().equals(Snip.class)) {
				if (!(obj instanceof String) && !(obj instanceof Class<?>) && !(obj instanceof DataSnip)) {
					EntityBundle bundle = DSMapper.getBundle(obj);
					Object id = bundle.getId(obj);
					if (id == null) {
						return;
					}
					for (PersistReferenceAttribute ref : bundle.getRefList()) {
						if (ref == null || ref.getBundle() == null) {
							continue;
						}
						if (ref.getBundle().isCross()) {
							snip(ref.getBundle().entity).where(ref.name, id).delete();
						} else {
							snip(ref.getBundle().entity).where(ref.name, id).what(ref.name, null).update();
						}
					}
				}
			}
		} catch (Exception e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	/**
	 * Deletes entitiy: parameter obj - may be String -entityName,Object - entityInstance used for invoking a class name , Class - entity class . Deletes an entity with attribute = value
	 */
	@Override
	public void delete(Object obj, String attribute, Object value) {
		try {
			DataSnip startSnip = startSnip(obj, SnipType.DELETE);
			instanceManager.provideChildrenLikeParent(startSnip, obj);
			execute(startSnip.where(attribute, value));
		} catch (Exception e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	/**
	 * Deletes entitiy: parameter obj - may be String -entityName,Object - entityInstance used for invoking a class name , Class - entity class . Deletes an entity with id = value
	 */
	@Override
	public void deleteById(Object obj, Object value) {
		try {
			DataSnip whereId = startSnip(obj, SnipType.DELETE).whereId(value);
			instanceManager.provideChildrenLikeParent(whereId, obj);
			execute(whereId);
		} catch (Exception e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	/**
	 * Drops table for given entity
	 */
	@Override
	public void drop(Object obj) {
		if (obj.getClass().equals(Snip.class)) {
			try {
				snipManager.execute((Snip) obj);
				return;
			} catch (Exception e) {
				throw new DataManagerException(getManagerName(), e);
			}
		}
		execute(startSnip(obj, SnipType.DROP));
	}

	/**
	 * Executes syncronously a query to SnipManager for purposes where not required data retrieve
	 */
	@Override
	public void execute(DataSnip dataSnip) {
		try {
			switch (DSSettings.getSnipPolicy()) {
				case EXECUTE:
					snipManager.execute(casheManager.getCachedSnip(dataSnip, transactionId));
					break;
				case EXECUTE_AND_LOG:
					snipManager.execute(casheManager.getCachedSnip(dataSnip, transactionId));
					SnipUtils.rebuildId(dataSnip);
					for (ISnipListener listener : listeners) {
						listener.snipExecuted(dataSnip);
					}
					break;
				case ONLY_LOG:
					if (listeners.size() == 0) {
						throw new DataManagerException("***Snup Policy ONLY_LOG but empty  List of <ISnipListener> - now datascooter will do nothing");
					}
					SnipUtils.rebuildId(dataSnip);
					for (ISnipListener listener : listeners) {
						listener.snipExecuted(dataSnip);
					}
					break;
			}
		} catch (Exception e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	/**
	 * Executes syncronously a query to SnipManager for purposes where not required data retrieve
	 */
	@Override
	public void executeAs(Snip snip, SnipType newType) {
		if (snip.getData().length > 0) {
			try {
				switch (newType) {
					case INSERT:
						snipManager.execute(getBuilderInt().saveSnipInt(snip, transactionId));
						break;
					case DELETE:
						snipManager.execute(getBuilderInt().deleteSnipInt(snip, transactionId));
						break;
					case UPDATE:
						snipManager.execute(getBuilderInt().updateSnipInt(snip, transactionId));
						break;
					default:
						throw new DataManagerException("***ExecuteAs purposed only for  INSERT UPDATE DELETE operations you operation :" + newType);
				}
			} catch (Exception e) {
				throw new DataManagerException(getManagerName(), e);
			}
		}
	}

	<T> T instSnip(DataSnip dataSnip) {
		try {
			Snip cachedSnip = casheManager.getCachedSnip(dataSnip, transactionId);
			try {
				return instanceManager.instantiateSnip(snipManager.retrieve(cachedSnip));
			} finally {
				cachedSnip.clearData();
			}
		} catch (Exception e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	<T> List<T> instSnipList(DataSnip dataSnip) {
		try {
			Snip cachedSnip = casheManager.getCachedSnip(dataSnip, transactionId);
			try {
				return instanceManager.instantiateSnipList(snipManager.retrieve(cachedSnip));
			} finally {
				cachedSnip.clearData();
			}
		} catch (Exception e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	/**
	 * Retrieves one first object for a given snip if it exists : parameter obj - may be String -entityName,Object - entityInstance used for invoking a class name , Class - entity class or
	 * DataSnipFactory.
	 */
	@Override
	public <T> T first(Object obj) {
		if (obj.getClass().equals(Snip.class)) {
			try {
				Snip retrieve = snipManager.retrieve((Snip) obj);
				try {
					return instanceManager.instantiateSnip(retrieve);
				} finally {
					retrieve.clearData();
				}
			} catch (Exception e) {
				throw new DataManagerException(getManagerName(), e);
			}
		}
		return instSnip(startSnip(obj, SnipType.SELECT));
	}

	/**
	 * Retrieves one first object for entity with given value of attribute : parameter obj - may be String -entityName,Object - entityInstance used for invoking a class name , Class - entity class .
	 */
	@Override
	public <T> T first(Object obj, String attribute, Object value) {
		return instSnip(startSnip(obj, SnipType.SELECT).where(attribute, value));
	}

	/**
	 * Retrieves one first object for entity with given value of identificator
	 */
	@Override
	public <T> T firstById(Object obj, Object value) {
		try {
			return instSnip(startSnip(obj, SnipType.SELECT).whereId(value));
		} catch (Exception e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T> T firstCross(Class<T> clazz, String crossEntityName, Object referenceEntity) {
		try {
			DataSnip select = new DataSnip(clazz.getName(), SnipType.SELECT);
			select.join(crossEntityName).join(referenceEntity.getClass().getName()).whereId(DSMapper.getIdValue(referenceEntity));
			List<Object> list = instSnipList(select);
			if (list.size() > 0) {
				return (T) list.get(0);
			}
			return null;
		} catch (Exception e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	@Override
	public BigDecimal function(Object obj, String attribute, Function function) {
		try {
			DataSnip snip = startSnip(obj, SnipType.FUNCTION);
			if (!(obj instanceof String) && !(obj instanceof Class<?>) && !(obj instanceof DataSnip)) {
				snip.whereId(DSMapper.getIdValue(obj));
			}
			snip.extraAttribute = attribute;
			snip.setFunction(function);
			Object[][] data = snipManager.retrieve(casheManager.getCachedSnip(snip, null)).getData();
			if (data.length > 0 && data[0].length > 0) {
				return ((BigDecimal) data[0][0]);
			}
			return BigDecimal.ZERO;
		} catch (Exception e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	/**
	 * Returns a current SnipBuilder - a tool for building queries to SnipManager
	 * 
	 * @return ISnipBuilder
	 */
	@Override
	public ISnipBuilder getBuilder() {
		return builder;
	}

	@Override
	public String getContextId() {
		return snipManager.getContextId();
	}

	/**
	 * Retrieves a current server date - usefull when you working with remote snipManager
	 */
	@Override
	public Date getCurrentDate() {
		return snipManager.getCurrentDate();
	}

	@Override
	public InstanceManager getInstanceManager() {
		return instanceManager;
	}

	@Override
	public String getName() {
		return getManagerName();
	}

	@Override
	public Snip getSnip(DataSnip dataSnip) {
		try {
			return casheManager.getCachedSnip(dataSnip, transactionId);
		} catch (Exception e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	/**
	 * Retrieves a list of entities: parameter obj - may be String -entityName,Object - entityInstance used for invoking a class name , Class - entity class or DataSnipFactory.
	 */
	@Override
	public <T> List<T> list(Object obj) {
		if (obj.getClass().equals(Snip.class)) {
			try {
				return instanceManager.instantiateSnipList(snipManager.retrieve((Snip) obj));
			} catch (Exception e) {
				throw new DataManagerException(getManagerName(), e);
			}
		}
		return instSnipList(startSnip(obj, SnipType.SELECT));
	}

	/**
	 * Retrieves a list of entities: parameter obj - may be String -entityName,Object - entityInstance used for invoking a class name , Class - entity class or DataSnipFactory. Restriction a size of
	 * list = from row during fetch size
	 */
	@Override
	public <T> List<T> list(Object obj, int fromRow, int fetchSize) {
		return list(obj, null, null, fromRow, fetchSize);
	}

	/**
	 * Retrieves a list of entities: parameter obj - may be String -entityName,Object - entityInstance used for invoking a class name , Class - entity class.
	 */
	@Override
	public <T> List<T> list(Object obj, String attribute, Object value) {
		return instSnipList(startSnip(obj, SnipType.SELECT).where(attribute, value));
	}

	/**
	 * Retrieves a list of entities: parameter obj - may be String -entityName,Object - entityInstance used for invoking a class name , Class - entity class . Restriction a size of list = from row
	 * during fetch size
	 */
	@Override
	public <T> List<T> list(Object obj, String attribute, Object value, int fromRow, int fetchSize) {
		DataSnip snip = startSnip(obj, SnipType.SELECT);
		snip.fromRow = fromRow;
		snip.fetchSize = fetchSize;
		if (attribute != null) {
			snip.where(attribute, value);
		}
		return instSnipList(snip);
	}

	/**
	 * Returns a list of cross referenced objects - many-to-many
	 * 
	 * @param <T>
	 * @param clazz
	 * @param crossEntity
	 * @param referenceEntity
	 * @return List<T>
	 */
	@Override
	public <T> List<T> listCross(Class<T> clazz, String crossEntity, Object referenceEntity) {
		try {
			DataSnip snip = new DataSnip(clazz.getName(), SnipType.SELECT);
			snip.join(crossEntity).join(referenceEntity.getClass().getName()).whereId(DSMapper.getIdValue(referenceEntity));
			return instSnipList(snip);
		} catch (Exception e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	/**
	 * Returns a list of references - objects referenced to this object - one-to-many
	 * 
	 * @param <T>
	 * @param clazz
	 * @param caller
	 * @return List<T>
	 */
	@Override
	public <T> List<T> listReferences(Class<T> clazz, Object caller) {
		try {
			DataSnip snip = new DataSnip(clazz.getName(), SnipType.SELECT);
			snip.join(caller.getClass().getName()).whereId(DSMapper.getIdValue(caller));
			return instSnipList(snip);
		} catch (Exception e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	@Override
	public double max(Object obj, String attribute) {
		return function(obj, attribute, Function.MAX).doubleValue();
	}

	@Override
	public double min(Object obj, String attribute) {
		return function(obj, attribute, Function.MIN).doubleValue();
	}

	@Override
	public void requestCommit() {
		snipManager.requestCommit();
	}

	/**
	 * Retrieves snip with data from syncronously query to SnipManager
	 */
	@Override
	public Snip retrieve(DataSnip dataSnip) {
		try {
			return snipManager.retrieve(casheManager.getCachedSnip(dataSnip, transactionId));
		} catch (Exception e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	/**
	 * Retrieves snip with data from syncronously query to SnipManager
	 */
	@Override
	public Snip retrieve(Snip snip) {
		try {
			return snipManager.retrieve(snip);
		} catch (Exception e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	@Override
	public void rollback() {
		try {
			snipManager.rollback();
			transactionId = null;
		} catch (Exception e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	@Override
	public void save(Object obj) {
		if (obj != null) {
			if (obj instanceof DataSnip) {
				execute((DataSnip) obj);
			} else {
				execute(instanceManager.flush(obj));
			}
		}
	}

	@Override
	public void saveIfNotExists(Object obj) {
		DataSnip dataSnip = instanceManager.flush(obj);
		if (count(obj) == 0) {
			execute(dataSnip);
		}
	}

	/**
	 * Save or update if you are not sure about object existence in the database
	 */
	@Override
	public void saveOrUpdate(Object obj) {
		try {
			if (Collection.class.isAssignableFrom(obj.getClass())) {
				Iterator<?> iterator = ((Collection<?>) obj).iterator();
				while (iterator.hasNext()) {
					saveOrUpdateInt(iterator.next());
				}
			} else if (obj.getClass().isArray()) {
				Object[] arr = (Object[]) obj;
				for (int a = 0; a < arr.length; a++) {
					saveOrUpdateInt(arr[a]);
				}
			} else {
				saveOrUpdateInt(obj);
			}
		} catch (Exception e) {
			e.printStackTrace();
			throw new DataManagerException(getManagerName(), e);
		}
	}

	private void saveOrUpdateInt(Object obj) throws EntityNotMappedException, NoSuchMethodException {
		DataSnip dataSnip = instanceManager.flush(obj);
		if (count(obj) > 0) {
			dataSnip.whereId(dataSnip.objectId);
			dataSnip.setSnipType(SnipType.UPDATE);
		}
		processFetch(dataSnip);
		execute(dataSnip);
	}

	private void processFetch(DataSnip dataSnip) {
		try {
			for (DataSnip snip : dataSnip.fetchList) {
				EntityBundle bundle = snip.bundle;
				if (bundle != null && bundle.id != null) {
					if (count(snip.entityName, bundle.id.name, snip.objectId) > 0) {
						snip.whereId(snip.objectId);
						snip.setSnipType(SnipType.UPDATE);
					}
				}
				processFetch(snip);
			}
		} catch (Exception e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	@Override
	public void saveOrUpdate(Snip snip) {
		try {
			if (snip.getData() != null) {
				saveOrUpdateInt(snip);
				Snip[] fetch = snip.getFetch();
				if (fetch != null) {
					for (Snip child : fetch) {
						if (child != null) {
							saveOrUpdate(child);
						}
					}
				}
			}
		} catch (Exception e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	private void saveOrUpdateInt(Snip snip) throws EntityNotMappedException, SecurityException, NoSuchMethodException, SnipManagerException {
		List<Object[]> insertList = new ArrayList<Object[]>();
		List<Object[]> updateList = new ArrayList<Object[]>();
		for (Object[] array : snip.getData()) {
			DataSnip snip2 = new DataSnip(LangUtils.entityName(snip.entity), SnipType.FUNCTION);
			snip2.setFunction(Function.COUNT);
			snip2.whereId(array[0]);
			if (count(snip2) > 0) {
				updateList.add(array);
			} else {
				insertList.add(array);
			}
		}
		String[] attrArray = DSMapper.getBundle(snip.entity).attrNameArray;
		if (insertList.size() > 0) {
			DataSnip result = new DataSnip(snip.entity, SnipType.INSERT);
			for (int a = 0; a < attrArray.length; a++) {
				result.what(attrArray[a], null);
			}
			Snip insertChild = getBuilderInt().build(result, transactionId);
			insertChild.setData(insertList.toArray(new Object[0][0]));
			insertChild.setFetch(null);
			snipManager.execute(insertChild);
		}
		if (updateList.size() > 0) {
			DataSnip result = new DataSnip(snip.entity, SnipType.UPDATE);
			for (int a = 1; a < attrArray.length; a++) {
				result.what(attrArray[a], null);
			}
			result.whereId("");
			Snip updateChild = getBuilderInt().build(result, transactionId);
			List<Snip> fetchList = new ArrayList<Snip>();
			for (Object[] item : updateList) {
				Object[] inverted = new Object[item.length];
				for (int a = 1; a < item.length; a++) {
					inverted[a - 1] = item[a];
				}
				inverted[item.length - 1] = item[0];
				Snip part = SnipFactory.copy(updateChild);
				part.setData(new Object[][] {
					inverted
				});
				fetchList.add(part);
			}
			Snip group = getBuilderInt().build(new DataSnip(null, SnipType.GROUP), transactionId);
			group.setFetch(fetchList.toArray(new Snip[0]));
			snipManager.execute(group);
		}
	}

	/**
	 * Saves a fields of named set of data - without any java class
	 */
	@Override
	public void saveSet(String entity, Object... fields) {
		try {
			DataSnip snipFromObject = SnipUtils.getSnipFromDataSet(entity, fields);
			snipFromObject.setSnipType(SnipType.INSERT);
			execute(snipFromObject);
		} catch (Exception e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	@Override
	public void setSnipPolicy(SnipPolicy snipPolicy) {
		DSSettings.setSnipPolicy(snipPolicy);
	}

	@Override
	public void setValidationPolicy(TableValidationPolicy validationPolicy) {
		DSSettings.setTableValidationPolicy(validationPolicy);
	}

	@Override
	public double sum(Object obj, String attribute) {
		return function(obj, attribute, Function.SUM).doubleValue();
	}

	/**
	 * Update whole object - when you are sure this object is exists in the database
	 */
	@Override
	public void update(Object obj) {
		if (obj == null) {
			return;
		}
		try {
			if (obj.getClass().equals(Snip.class)) {
				snipManager.execute((Snip) obj);
			} else if (obj instanceof DataSnip) {
				execute((DataSnip) obj);
			} else {
				DataSnip dataSnip = instanceManager.flush(obj);
				Object idValue = DSMapper.getIdValue(obj);
				dataSnip.whereId(idValue);
				dataSnip.setSnipType(SnipType.UPDATE);
				execute(dataSnip);
			}
		} catch (Exception e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	/**
	 * Update only one from attributes of object
	 */
	@Override
	public void update(Object obj, String attribute, Object value) {
		try {
			if (obj instanceof Class) {
				execute(startSnip(obj, SnipType.UPDATE).what(attribute, value));
			} else {
				execute(startSnip(obj, SnipType.UPDATE).whereId(DSMapper.getIdValue(obj)).what(attribute, value));
			}
		} catch (Exception e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	/**
	 * Verify table - check if it exists, check it columns on existence and type
	 * 
	 * @param bundle
	 */
	@Override
	public void verifyTable(EntityBundle bundle) {
		try {
			new ClientTableManager(this).verifyTable(bundle);
		} catch (Exception e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	@Override
	public IdManager getIdManager() {
		return idManager;
	}

	@Override
	public IBackupManager getBackup() {
		return new BackupManager(snipManager, getBuilderInt());
	}

	@Override
	public boolean tableExists(Object obj) {
		try {
			return new ClientTableManager(this).tableExists(obj);
		} catch (Exception e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	@Override
	public void dropAll(String shemaName, boolean include, String... tableNames) {
		try {
			new ClientTableManager(this).dropAll(shemaName, include, tableNames);
		} catch (Exception e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	@Override
	public void clearAll(String shemaName, boolean include, String... tableNames) {
		try {
			new ClientTableManager(this).clearAll(shemaName, include, tableNames);
		} catch (Exception e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	@Override
	public void addBundle(EntityBundle bundle, boolean checkTable) throws EntityNotMappedException {
		DSMapper.addBundleInt(bundle);
		if (checkTable) {
			verifyTable(bundle);
		}
	}

	@Override
	public ISnipManager getSnipManager() {
		return snipManager;
	}

	@Override
	public SnipPolicy getSnipPolicy() {
		return DSSettings.getSnipPolicy();
	}

	@Override
	public String getTransactionId() {
		return transactionId;
	}

	@Override
	public TableValidationPolicy getValidationPolicy() {
		return DSSettings.getTableValidationPolicy();
	}

	@Override
	public void showSQL(boolean isShow) {
		snipManager.showSQL(isShow);
	}

	@Override
	public void showRetrieveSQL(boolean isShow) {
		snipManager.showRetrieveSQL(isShow);
	}

	@Override
	public SimpleSnipCashe getSnipCashe() {
		return casheManager;
	}

	@Override
	public Object retrieveData(DataSnip dataSnip) {
		try {
			Snip retrieve = retrieve(dataSnip);
			try {
				return instanceManager.instantiate(retrieve);
			} finally {
				retrieve.clearData();
			}
		} catch (Exception e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	@Override
	public void execute(Snip snip) {
		try {
			snipManager.execute(snip);
		} catch (Exception e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	@Override
	public <T> List<T> getCross(Object container, String containerAttribute) {
		return crossProvider.getContent(container, containerAttribute);
	}

	@Override
	public void saveCross(Object container, String containerAttribute, Object... content) {
		try {
			crossProvider.saveContent(container, containerAttribute, content);
		} catch (Exception e) {
			throw new DataManagerException(getManagerName(), e);
		}
	}

	@Override
	public void clearCross() {
		crossProvider.clearCross();
	}

	@Override
	public void removeCross(Object container, String containerAttribute, Object... content) {
		crossProvider.removeContent(container, containerAttribute, content);
	}

	@Override
	public int countContent(Object container, String containerAttribute) {
		return crossProvider.countContent(container, containerAttribute);
	}

	@Override
	public int countContainer(Class<?> containerClass, Object content, String containerAttribute) {
		return crossProvider.countContainer(containerClass, content, containerAttribute);
	}

	@Override
	public DataSnip getContainerDataSnip(Class<?> containerClass, Object content, String containerAttribute) {
		return crossProvider.getContainerDataSnip(containerClass, content, containerAttribute);
	}

	@Override
	public <T> List<T> getContainerList(Class<T> containerClass, Object content, String containerAttribute) {
		return crossProvider.getContainerList(containerClass, content, containerAttribute);
	}

	/**
	 * Be careful to ensure what you want - if you where send as param not string or class - changes would be applied to exactly one object
	 */
	@Override
	public DataSnip snip(Object obj) {
		DataSnip snip;
		if (obj instanceof DataSnip) {
			snip = (DataSnip) obj;
		} else {
			snip = new DataSnip(LangUtils.entityName(obj), SnipType.SELECT);
			if (obj instanceof String) {
			} else if (obj instanceof Class<?>) {
			} else {
				try {
					snip.whereId(DSMapper.getIdValue(obj));
				} catch (Exception e) {
					throw new DataManagerException(getManagerName(), e);
				}
			}
		}
		snip.setDataManager(this);
		return snip;
	}

	@Override
	public IReplicationManager getReplication() {
		return new ReplicationManager(this);
	}

	@Override
	public String toString() {
		return "DataManager [managerName=" + getManagerName() + "]";
	}

	private String getManagerName() {
		return snipManager.getName();
	}

	@Override
	public void verifyReferences(ReferencePolicy policy) {
		List<EntityBundle> bundleList = DSMapper.getBundleList();
		for (EntityBundle bundle : bundleList) {
			if (bundle.table == null) {
				continue;
			}
			List<Object> list = snip(bundle.entity).list();
			Iterator<PersistReferenceAttribute> iterator = bundle.getRefEntityMap().values().iterator();
			while (iterator.hasNext()) {
				PersistReferenceAttribute next = iterator.next();
				for (Object obj : list) {
					Object value = null;
					try {
						value = next.getValue(obj);
					} catch (Exception e) {
						throw new DataScooterException(e);
					}
					if (value != null) {
						if (snip(next.getEntity()).whereId(value).count() == 0) {
							switch (policy) {
								case ERROR_IF_NOT:
									throw new DataScooterException("***Wrong reference :" + bundle.entity + " to " + next.name + "  value:  " + value);
								case NULL_IF_NOT:
									try {
										next.setValue(obj, null);
										update(obj);
									} catch (Exception e) {
										throw new DataScooterException(e);
									}
									break;
								case LOG_IF_NOT:
									Logger.getLogger(DataManager.class.getName()).info("***Wrong reference :" + bundle.entity + " reference -  " + next.name + "  value:  " + value);
									break;
								case NULL_AND_LOG_IF_NOT:
									try {
										next.setValue(obj, null);
										update(obj);
									} catch (Exception e) {
										throw new DataScooterException(e);
									}
									Logger.getLogger(DataManager.class.getName()).info("***Wrong reference was set to null  :" + bundle.entity + " reference -  " + next.name + "  value:  " + value);
									break;
							}
						}
					}
				}
			}
		}
	}

	@Override
	public void provideId(Object obj) {
		EntityBundle bundle = DSMapper.getBundle(obj);
		if (bundle != null && bundle.id != null) {
			bundle.provideId(obj, this);
		}
	}
}
